/**
 * 
 */
package uk.ac.ebi.velvet.config;

import java.io.File;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javolution.util.FastList;
import javolution.util.FastMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.utils.file.FileInfo;
import uk.ac.ebi.velvet.model.ContigData;
import uk.ac.ebi.velvet.model.MapLocation;
import uk.ac.ebi.velvet.model.ReadData;

/**
 * @author mhaimel
 *
 */
public class VelvetData implements VelvetDataAccessService, Serializable {
	private Log log = LogFactory.getLog(this.getClass());
	private int ctgIdxId = 0;
	private int readIdxId = 0;
	
	private VelvetArgs velArgs;
	private Map<String, Integer> readIdentifierIdx = new FastMap<String, Integer>();
	private SortedMap<Long, File> position2fileIndex = new TreeMap<Long, File>();
	private Map<File, Long> file2positionIndex = new HashMap<File, Long>();
	private Map<String, Integer> contigIdentifierIdx = new FastMap<String, Integer>();
	private Map<Integer, String> identifier2ReadIdx = new FastMap<Integer, String>();
	private Map<Integer, String> identifier2ContigIdx = new FastMap<Integer, String>();
	private ContigData[] ctgDataArr = new ContigData[0];
	private ReadData[] readArr = new ReadData[0];
	private List<MapLocation> locations = new FastList<MapLocation>();

	public VelvetData() {
		
	}

	public SortedMap<Long, File> getPosition2FileIndex() {
		return position2fileIndex;
	}

	private Map<File, Long> getFile2PositionIndex() {
		return file2positionIndex;
	}
	public VelvetArgs getVelvetArgs() {
		return velArgs;
	}
	public void setVelvetArgs(VelvetArgs velArgs) {
		this.velArgs = velArgs;
	}	
	public Map<String, Integer> getReadIdentifierIdx() {
		return readIdentifierIdx;
	}
	public Map<String, Integer> getContigIdentifierIdx() {
		return contigIdentifierIdx;
	}
	public void setReadIdentifierIdx(Map<String, Integer> readIdentifierIdx) {
		this.readIdentifierIdx = readIdentifierIdx;
	}
	public void setContigIdentifierIdx(Map<String, Integer> contigIdentifierIdx) {
		this.contigIdentifierIdx = contigIdentifierIdx;
	}
	public ContigData[] getContigDataArr() {
		return ctgDataArr;
	}
	public Collection<ContigData> getContigData() {
		return Arrays.asList(ctgDataArr);
	}
	public void setContigData(ContigData[] refDataArr) {
		this.ctgDataArr = refDataArr;
	}
	public ReadData[] getReadData() {
		return readArr;
	}
	public void setReadData(ReadData[] shortReadArr) {
		this.readArr = shortReadArr;
	}
	public List<MapLocation> getLocations() {
		return locations;
	}
	public void setLocations(List<MapLocation> locations) {
		this.locations = locations;
	}	
	
	@Override
	public void addLocation(MapLocation loc) {
		getLocations().add(loc);
	}
	
	public String printStatus() {
		StringBuffer b = new StringBuffer();
		b.append("Contig data:\t" + getContigDataArr().length+"\n");
		b.append("Short read ids:\t" + getReadData().length+"\n");
		b.append("Location links:\t" + getLocations().size()+"\n");
		return b.toString();
	}
	
	@Override
	public Integer getContigIdentifier(String key) {
		return getContigIdentifierIdx().get(key);
	}
	@Override
	public int getLocationSize() {
		return getLocations().size();
	}
	@Override
	public Integer getReadIdentifier(String id) {
		return getReadIdentifierIdx().get(id);
	}
	@Override
	public Integer addContigIdentifier(String id) {
		Integer idx = getContigIdentifier(id);
		if(null == idx){
			idx = nextContigId();
			getContigIdentifierIdx().put(id, idx);
		}
		return idx;
	}
	
	private int nextContigId() {
		return ctgIdxId++;
	}
	
	private int nextReadId() {
		return readIdxId++;
	}
	
	@Override
	public Integer addReadIdentifier(String id) {
		Integer idx = getReadIdentifier(id);
		if(null == idx){
			idx = nextReadId();
			getReadIdentifierIdx().put(id, idx);
		}
		return idx;
	}
	@Override
	public int getContigSize() {
		return ctgIdxId;
	}
	@Override
	public int getReadSize() {
		return readIdxId;
	}
	@Override
	public ContigData getContig(Integer id) {
		int len = getContigDataArr().length;
		ContigData ctg = null;
		if(id < len){
			ctg = getContigDataArr()[id];
			if(null == ctg){
				getLog().warn("Contig id " + id + " is in range but does not exist!");
			}
		} else {
			getLog().warn("Contig id " + id + " is out of range from " + len);
		}
		return ctg;
	}
	@Override
	public ReadData getRead(Integer id) {
		int len = getReadData().length;
		ReadData readData = null;
		if(id < len){
			readData = getReadData()[id];
			if(null == readData){
				getLog().warn(
						"Read with id is in range but does not exist: " + id);
			}	
		} else {
			getLog().warn("Read of id is out of range: " + id + " from " + len);
		}
		return readData;
	}
	
	@Override
	public FileInfo getFileForPosition(long globalPosition){
		File file = null;
		synchronized (position2fileIndex) {
			SortedMap<Long, File> headMap = getHeadMap(globalPosition);
			file = headMap.get(headMap.lastKey());
		}
		FileInfo fileInfo = getVelvetArgs().getFileinfoForFile(file);
		return fileInfo;
	}
	
	private SortedMap<Long, File> getHeadMap(long globalPosition) {
		return getPosition2FileIndex().headMap(globalPosition+1);
	}	

	@Override
	public long getGlobalFilePosition(File info) {
		synchronized (position2fileIndex) {
			Long gPos = getFile2PositionIndex().get(info);
			if(null == gPos){
				// INFO assume at this point, that this code is NOT run in parallel
				gPos = addGlobalFileEntry(info);			
			}
			return gPos;
		}
	}

	@Override
	public Long translate2LocalPosition(long globalPosition) {
		FileInfo fileInfo = getFileForPosition(globalPosition);
		long localPos = getGlobalFilePosition(fileInfo.getFile());
		return globalPosition-localPos;
	}
	
	private Long addGlobalFileEntry(File info) {
		Long nextKey = 0L;
		if(getPosition2FileIndex().size() > 0){
			Long lastKey = getPosition2FileIndex().lastKey();
			File lastFile = getPosition2FileIndex().get(lastKey);
			nextKey = lastKey+lastFile.length();
		}
		getPosition2FileIndex().put(nextKey, info);
		getFile2PositionIndex().put(info, nextKey);
		return nextKey;
	}
	
	public void clearAll(){
		readIdentifierIdx.clear();
		readIdentifierIdx = null;
		
		position2fileIndex.clear();
		position2fileIndex = null;
		
		contigIdentifierIdx.clear();
		contigIdentifierIdx = null;
		
		identifier2ContigIdx.clear();
		identifier2ContigIdx = null;
		
		identifier2ReadIdx.clear();
		identifier2ReadIdx = null;
		
		locations.clear();
		locations = null;
		
		ctgDataArr=null;
		velArgs = null;
	}
	
	private Log getLog() {
		return log;
	}

}
